package drds.server.buffer;

import java.nio.ByteBuffer;
import java.util.Set;
import java.util.concurrent.ConcurrentSkipListSet;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 仿照Netty的思路，针对内存缓冲策略优化 ChunkList维护着一个指向一串Chunk的头结点，访问策略由minUsage，maxUsage决定
 * 
 * @author 曹正辉<330937205@qq.com>
 * 
 */
public class BlockList {
	private static final Logger LOGGER = LoggerFactory.getLogger(BlockList.class);
	// 当前list中的chunk最小使用比例
	private final int minUsage;
	// 当前list中的chunk最大使用比例
	private final int maxUsage;

	Set<Block> blockSet;
	BlockList prev;
	BlockList next;

	public BlockList(int blockSize, int pageSize,//
			int minUsage, int maxUsage,//
			int blockNum) {
		this.minUsage = minUsage;
		this.maxUsage = maxUsage;
		this.blockSet = new ConcurrentSkipListSet<Block>();
		for (int i = 0; i < blockNum; i++) {
			Block block = new Block(blockSize, pageSize);
			this.blockSet.add(block);
		}
	}

	public Block getBlockFrom(ByteBuffer byteBuffer) {
		for (Block block : blockSet) {
			if (block.isIn(byteBuffer)) {
				return block;
			}
		}
		return null;
	}

	ByteBuffer allocateByteBuffer(int capacity) {
		for (Block block : blockSet) {
			ByteBuffer byteBuffer = block.allocateByteBuffer(capacity);
			if (byteBuffer == null) {
				continue;
			} else {// get ByteBuffer
				// this.block
				final int usage = block.usage();
				if (usage >= this.maxUsage) {
					// 从next.BlockList获取
					BlockList next = this.next;
					BlockList $ = this;// 中间对象
					while (next != null) {
						$.blockSet.remove(block);
						next.blockSet.add(block);
						//
						if (usage < next.maxUsage) {
							break;
						}
						// right
						$ = next;
						next = next.next;
					}//
				}// end if
				return byteBuffer;
			}
		}
		return null;
	}

	/**
	 * <pre>
	 * 当一个chunk的用量超过一定的比例，会将该chunk从当前list挪到下一个list中，这样挪有什么好处呢？ 
	 * 我们知道chunk本身是从连续的内存中分配一小段连续的内存，这样实际使用内存者读写很方便，
	 * 然而这种策略也带来了一个坏处，随着内存的不断分配和回收，chunk中可能存在很多碎片。 
	 * 碎片越来越多后我们想分配一段连续内存的失败几率就会提高。
	 * 针对这种情况我们可以把使用比例较大的chunk放到更后面，而先从使用比例更小的chunk中更早，这样成功的几率就提高了。
	 * 然而光把chunk往后放是不科学的，因为随着内存的释放，原先被严重瓜分的chunk中会存在越来越多的大块连续内存，所以还得在特定条件下把chunk从后往前调。
	 * 调整的时机当然就是在内存释放的时候了。
	 * </pre>
	 */
	boolean free(ByteBuffer byteBuffer) {
		Block block = getBlockFrom(byteBuffer);
		if (block == null) {
			LOGGER.info("not in this list!");
			return false;
		}
		block.freeByteBuffer(byteBuffer);
		final int usage = block.usage();
		// 用量少于阈值则从当前list移到前一个list,如果不存在前一个list,则销毁chunk
		if (usage < this.minUsage) {
			BlockList prev = this.prev;
			BlockList $this = this;
			while (prev != null) {
				$this.blockSet.remove(block);
				prev.blockSet.add(block);
				// 直到不能再优化
				if (usage > prev.minUsage) {

					break;
				}
				//
				$this = prev;
				prev = prev.prev;
			}
		}
		return true;
	}
}
